package com.htpcremote;

import java.net.InetAddress;
import java.net.URLEncoder;
import java.util.Timer;
import java.util.TimerTask;
import android.content.Context;
import android.os.Bundle;
import android.os.PowerManager;
import android.text.Editable;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.ImageButton;

import com.illposed.osc.OSCMessage;
import com.illposed.osc.OSCPortOut;

public final class PadActivity extends BaseActivity {
	//
	private static final int TAP_NONE = 0;
	private static final int TAP_FIRST = 1;
	private static final int TAP_SECOND = 2;
	private static final int TAP_DOUBLE = 3;
	private static final int TAP_DOUBLE_FINISH = 4;
	private static final String TAG = "HTPCRemote";

	//
	private OSCPortOut sender;
	
	//
	private boolean leftToggle = false;
	//
	private boolean rightToggle = false;
	private float xHistory;
	private float yHistory;
	//
	private int lastPointerCount = 0;
	// power lock
	private PowerManager.WakeLock lock;

	// toggles
	private boolean toggleButton = false;
	// tap to click
	private long lastTap = 0;
	private int tapState = TAP_NONE;
	private Timer tapTimer;
	// multitouch scroll
	// private float scrollX = 0f;
	private float scrollY = 0f;
	private double mMouseSensitivityPower;
	private static final float sScrollStepMax = 6f;
	private static final float sScrollStepMin = 45f;
	private static final float sScrollMaxSettingsValue = 100f;
	private float mScrollStep;// = 12f;
	private boolean mIsMultitouchEnabled;

	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		Settings.init(this.getApplicationContext());
		// Hide the title bar
		requestWindowFeature(Window.FEATURE_NO_TITLE);
		getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
				WindowManager.LayoutParams.FLAG_FULLSCREEN);
		if (this.lock == null) {
			Context appContext = this.getApplicationContext();
			// get wake lock
			PowerManager manager = (PowerManager) appContext
					.getSystemService(Context.POWER_SERVICE);
			this.lock = manager.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, this
					.getString(R.string.app_name));

			this.mIsMultitouchEnabled = WrappedMotionEvent.isMutitouchCapable();
		}
		//
		try {
			//
			setContentView(R.layout.pad_layout);
			DisplayMetrics dm = new DisplayMetrics();
			this.getWindowManager().getDefaultDisplay().getMetrics(dm);
			//
			this.sender = new OSCPortOut(InetAddress.getByName(Settings.ip), 48727);
			//
			this.initTouchpad();
			this.initScrollpad();
			this.initLeftButton();
			this.initRightButton();
			this.initSendButton();
			this.initMediaButtons();
		} catch (Exception ex) {
			Log.d(TAG, ex.toString());
		}
	}

	private void refreshSettings() {
		// Setup accelerations
		mMouseSensitivityPower = 1 + ((double) Settings.sensitivity) / 100d;
		mScrollStep = (sScrollStepMin - sScrollStepMax)
				* (sScrollMaxSettingsValue - Settings.scrollSensitivity) / sScrollMaxSettingsValue
				+ sScrollStepMax;
	}

	private void initTouchpad() {
		FrameLayout fl = (FrameLayout) this.findViewById(R.id.flTouchPad);
		fl.setOnTouchListener(new View.OnTouchListener() {
			public boolean onTouch(View v, MotionEvent ev) {
				return onMouseMove(ev);
			}
		});
	}
	
	private void initScrollpad() {
		FrameLayout fl = (FrameLayout) this.findViewById(R.id.flScrollPad);
		fl.setOnTouchListener(new View.OnTouchListener() {
			public boolean onTouch(View v, MotionEvent ev) {
				return onScrollMove(ev);
			}
		});
	}

	private void initLeftButton() {
		Button button = (Button) this.findViewById(R.id.btnLeftButton);
		button.setOnTouchListener(new View.OnTouchListener() {
			public boolean onTouch(View v, MotionEvent ev) {
				return onLeftTouch(ev);
			}
		});
	}

	private void initRightButton() {
		Button button = (Button) this.findViewById(R.id.btnRightButton);
		button.setOnTouchListener(new View.OnTouchListener() {
			public boolean onTouch(View v, MotionEvent ev) {
				return onRightTouch(ev);
			}
		});
	}

	private void initSendButton() {
		Button button = (Button) this.findViewById(R.id.btnSend);
		final EditText text = (EditText) this.findViewById(R.id.edtInput);
		button.setOnClickListener(new View.OnClickListener() {
			public void onClick(View v) {
				Editable editable = text.getText();
				sendText(editable.toString());
				editable.clear();
			}
		});
	}
	
	private void initMediaButtons() {
		this.setImageButtonKeycode(R.id.btnNext, 0xB0);
		this.setImageButtonKeycode(R.id.btnPrevious, 0xB1);
		this.setImageButtonKeycode(R.id.btnStop, 0xB2);
		this.setImageButtonKeycode(R.id.btnPlayPause, 0xB3);
		this.setImageButtonKeycode(R.id.btnMute, 0xAD);
		this.setImageButtonKeycode(R.id.btnVolumeDown, 0xAE);
		this.setImageButtonKeycode(R.id.btnVolumeUp, 0xAF);
		this.setTextButtonKeycode(R.id.btnEnter, 0x0D);
		this.setTextButtonKeycode(R.id.btnEscape, 0x1B);
		this.setTextButtonKeycode(R.id.btnBackspace, 0x08);

		ImageButton button = (ImageButton) this.findViewById(R.id.btnMagnifier);
		button.setOnClickListener(new View.OnClickListener() {
			public void onClick(View v) {
				sendMessage("/magnifier", null);
			}
		});
	}

	private void setImageButtonKeycode(int id, final int keycode) {
		ImageButton button = (ImageButton) this.findViewById(id);
		button.setOnClickListener(new View.OnClickListener() {
			public void onClick(View v) {
				sendKeyboard(keycode);
			}
		});
	}

	private void setTextButtonKeycode(int id, final int keycode) {
		Button button = (Button) this.findViewById(id);
		button.setOnClickListener(new View.OnClickListener() {
			public void onClick(View v) {
				sendKeyboard(keycode);
			}
		});
	}

	public void onStart() {
		super.onStart();
		this.refreshSettings();
	}

	public void onResume() {
		super.onResume();
		// acquire screen lock
		this.lock.acquire();
		this.refreshSettings();
	}

	public void onPause() {
		super.onPause();
		// this'd be a great time to disconnect from the server, and clean
		// up anything that needs to be cleaned up.
		// release screen lock
		this.lock.release();
	}

	public void onDestroy() {
		super.onDestroy();
		this.sender.close();
	}
	
	private void sendMessage(String address, Object[] args) {
		OSCMessage msg = new OSCMessage(address, args);
		try {
			this.sender.send(msg);
		} catch (Exception ex) {
			Log.d(TAG, ex.toString());
		}
	}

	// Text
	private void sendText(String text) {
		Object[] args = new Object[1];
		args[0] = URLEncoder.encode(text);
		this.sendMessage("/text", args);
	}
	
	// keyboard
	private void sendKeyboard(int keycode) {
		Object[] args = new Object[1];
		args[0] = keycode;
		this.sendMessage("/keyboard", args);
	}

	private void sendMouseEvent(float x, float y) {
		//
		float xDir = x == 0 ? 1 : x / Math.abs(x);
		float yDir = y == 0 ? 1 : y / Math.abs(y);
		//
		Object[] args = new Object[2];
		args[0] = (float) (Math.pow(Math.abs(x), mMouseSensitivityPower)) * xDir;
		args[1] = (float) (Math.pow(Math.abs(y), mMouseSensitivityPower)) * yDir;

		this.sendMessage("/mouse", args);
	}

	private void sendScrollEvent() {
		if (Math.abs(scrollY) > mScrollStep) {
			Object[] args = new Object[1];
			args[0] = (scrollY > 0f) ? 1 : -1;
			this.sendMessage("/wheel", args);

			scrollY = 0f;
		}
	}

	private boolean onMouseMove(MotionEvent ev) {
		int type = 0;
		float xMove = 0f;
		float yMove = 0f;

		int pointerCount = 1;
		if (mIsMultitouchEnabled) {
			pointerCount = WrappedMotionEvent.getPointerCount(ev);
		}

		switch (ev.getAction()) {
		case MotionEvent.ACTION_DOWN:
			//
			if (Settings.tapToClick && (pointerCount == 1)) {
				if (this.tapState == TAP_NONE) {
					// first tap
					this.lastTap = System.currentTimeMillis();
					//
				} else if (this.tapState == TAP_FIRST) {
					// second tap - check if we've fired the button up
					if (this.tapTimer != null) {
						// up has not been fired
						this.tapTimer.cancel();
						this.tapTimer = null;
						this.tapState = TAP_SECOND;
						this.lastTap = System.currentTimeMillis();
					}
				}
			}
			//
			type = 0;
			xMove = 0;
			yMove = 0;
			//
			this.xHistory = ev.getX();
			this.yHistory = ev.getY();
			//
			break;
		case MotionEvent.ACTION_UP:
			if (Settings.tapToClick && (pointerCount == 1)) {
				// it's a tap!
				long now = System.currentTimeMillis();
				long elapsed = now - this.lastTap;
				if (elapsed <= Settings.clickTime) {
					if (this.tapState == TAP_NONE) {
						// send the mouse down event
						this.lastTap = now;
						//
						this.tapTimer = new Timer();
						this.tapTimer.scheduleAtFixedRate(new TimerTask() {
							public void run() {
								firstTapUp();
							}
						}, 0, Settings.clickTime);

					} else if (this.tapState == TAP_SECOND) {
						// double-click
						this.tapTimer = new Timer();
						this.tapTimer.scheduleAtFixedRate(new TimerTask() {
							public void run() {
								secondTapUp();
							}
						}, 0, 10);
					}

				} else {
					// too long
					this.lastTap = 0;
					if (this.tapState == TAP_SECOND) {
						// release the button
						this.tapState = TAP_NONE;
						this.lastTap = 0;
						this.leftButtonUp();
					}
				}
			}
			//
			type = 1;
			xMove = 0;
			yMove = 0;
			break;
		case MotionEvent.ACTION_MOVE:
			if (pointerCount == 1) {
				// move
				type = 2;
				if (lastPointerCount == 1) {
					xMove = ev.getX() - this.xHistory;
					yMove = ev.getY() - this.yHistory;
				}
				this.xHistory = ev.getX();
				this.yHistory = ev.getY();
			} else if (pointerCount == 2) {
				// multitouch scroll
				type = -1;

				int pointer0 = WrappedMotionEvent.getPointerId(ev, 0);
				int pointer1 = WrappedMotionEvent.getPointerId(ev, 1);

				// float posX = WrappedMotionEvent.getX(ev, pointer0);
				float posY = WrappedMotionEvent.getY(ev, pointer0);

				// only consider the second pointer if I had a previous history
				if (lastPointerCount == 2) {
					// posX += WrappedMotionEvent.getX(ev, pointer1);
					// posX /= 2;
					posY += WrappedMotionEvent.getY(ev, pointer1);
					posY /= 2;

					// xMove = posX - this.xHistory;
					yMove = posY - this.yHistory;
				} else {
					// xMove = posX - this.xHistory;
					yMove = posY - this.yHistory;

					// posX += WrappedMotionEvent.getX(ev, pointer1);
					// posX /= 2;
					posY += WrappedMotionEvent.getY(ev, pointer1);
					posY /= 2;
				}

				// this.xHistory = posX;
				this.yHistory = posY;
			}
			break;
		}
		if (type == -1) {
			scrollY += yMove;
			this.sendScrollEvent();
		} else if (type == 2) {
			// if type is 0 or 1, the server will not do anything with it, so we
			// only send type 2 events
			this.sendMouseEvent(xMove, yMove);
		}
		lastPointerCount = pointerCount;
		return true;
	}
	
	private boolean onScrollMove(MotionEvent ev) {
		switch (ev.getAction()) {
		case MotionEvent.ACTION_DOWN:
			this.yHistory = ev.getY();
			//
			break;
		case MotionEvent.ACTION_MOVE:
			scrollY += (ev.getY() - this.yHistory);
			this.yHistory = ev.getY();
			this.sendScrollEvent();
			break;
		}
		
		return true;
	}

	//

	private void firstTapUp() {
		this.leftToggle = false;
		if (this.tapState == TAP_NONE) {
			// single click
			// counts as a tap
			this.tapState = TAP_FIRST;
			this.leftButtonDown();
		} else if (this.tapState == TAP_FIRST) {
			this.leftButtonUp();
			this.tapState = TAP_NONE;
			this.lastTap = 0;
			this.tapTimer.cancel();
			this.tapTimer = null;
		}
	}

	private void secondTapUp() {
		this.leftToggle = false;
		if (this.tapState == TAP_SECOND) {
			// mouse up
			this.leftButtonUp();
			this.lastTap = 0;
			this.tapState = TAP_DOUBLE;
		} else if (this.tapState == TAP_DOUBLE) {
			this.leftButtonDown();
			this.tapState = TAP_DOUBLE_FINISH;
		} else if (this.tapState == TAP_DOUBLE_FINISH) {
			this.leftButtonUp();
			this.tapState = TAP_NONE;
			this.tapTimer.cancel();
			this.tapTimer = null;
		}
	}

	private void moveMouseWithSecondFinger(MotionEvent ev) {
		if (!mIsMultitouchEnabled) {
			return;
		}
		int pointerCount = WrappedMotionEvent.getPointerCount(ev);
		// if it is a multitouch move event
		if (pointerCount == 2) {
			// int pointer0 = ev.getPointerId(0);
			int pointer1 = WrappedMotionEvent.getPointerId(ev, 1);

			float x = WrappedMotionEvent.getX(ev, pointer1);
			float y = WrappedMotionEvent.getY(ev, pointer1);

			if (lastPointerCount == 2) {
				float xMove = x - this.xHistory;
				float yMove = y - this.yHistory;

				this.sendMouseEvent(xMove, yMove);
			}
			this.xHistory = x;
			this.yHistory = y;
		}
		lastPointerCount = pointerCount;
	}
	
	private boolean onLeftTouch(MotionEvent ev) {
		switch (ev.getAction()) {
		case MotionEvent.ACTION_DOWN:
			//
			if (this.toggleButton == false) {
				if (this.leftToggle) {
					this.leftButtonUp();
					this.leftToggle = false;
				}
				this.leftButtonDown();
			}
			break;
		case MotionEvent.ACTION_UP:
			//
			if (this.toggleButton == false) {
				this.leftButtonUp();
			} else {
				if (this.leftToggle) {
					this.leftButtonUp();
				} else {
					this.leftButtonDown();
				}
				this.leftToggle = !this.leftToggle;
			}
			break;
		case MotionEvent.ACTION_MOVE:
			moveMouseWithSecondFinger(ev);
			break;
		}
		//
		return false;
	}

	private synchronized void leftButtonDown() {
		Object[] args = new Object[1];
		args[0] = 0;
		OSCMessage msg = new OSCMessage("/leftbutton", args);
		try {
			this.sender.send(msg);
		} catch (Exception ex) {
			Log.d(TAG, ex.toString());
		}
	}

	private synchronized void leftButtonUp() {
		Object[] args = new Object[1];
		args[0] = 1;
		OSCMessage msg = new OSCMessage("/leftbutton", args);
		try {
			this.sender.send(msg);
		} catch (Exception ex) {
			Log.d(TAG, ex.toString());
		}
	}

	private boolean onRightTouch(MotionEvent ev) {
		switch (ev.getAction()) {
		case MotionEvent.ACTION_DOWN:
			//
			if (this.toggleButton == false) {
				if (this.rightToggle) {
					this.rightButtonUp();
					this.rightToggle = false;
				}
				this.rightToggle = false;
				this.rightButtonDown();
			}
			break;
		case MotionEvent.ACTION_UP:
			//
			if (this.toggleButton == false) {
				this.rightButtonUp();
			} else {
				// toggle magic!
				if (this.rightToggle) {
					this.rightButtonUp();
				} else {
					this.rightButtonDown();
				}
				this.rightToggle = !this.rightToggle;
			}
			break;
		case MotionEvent.ACTION_MOVE:
			moveMouseWithSecondFinger(ev);
			break;
		}
		//
		return false;
	}

	private void rightButtonDown() {
		Object[] args = new Object[1];
		args[0] = 0;
		OSCMessage msg = new OSCMessage("/rightbutton", args);
		try {
			this.sender.send(msg);
		} catch (Exception ex) {
			Log.d(TAG, ex.toString());
		}
	}

	private void rightButtonUp() {
		Object[] args = new Object[1];
		args[0] = 1;
		OSCMessage msg = new OSCMessage("/rightbutton", args);
		try {
			this.sender.send(msg);
		} catch (Exception ex) {
			Log.d(TAG, ex.toString());
		}
	}
}
